home *** CD-ROM | disk | FTP | other *** search
- import totem
- import gobject, gtk, gio, gconf
- gobject.threads_init()
- import xmlrpclib
- import threading
- import xdg.BaseDirectory
- from os import sep
- import gettext
-
- from hash import hashFile
-
- D_ = gettext.dgettext
-
- USER_AGENT = 'Totem'
- OK200 = '200 OK'
- TOTEM_REMOTE_COMMAND_REPLACE = 14
-
- SUBTITLES_EXT = [
- "asc",
- "txt",
- "sub",
- "srt",
- "smi",
- "ssa",
- "ass",
- ]
-
- # Map of the language codes used by opensubtitles.org's API to their human-readable name
- LANGUAGES_STR = [(D_('iso_639_3', 'Albanian'), 'sq'),
- (D_('iso_639_3', 'Arabic'), 'ar'),
- (D_('iso_639_3', 'Armenian'), 'hy'),
- (D_('iso_639_3', 'Neo-Aramaic, Assyrian'), 'ay'),
- (D_('iso_639_3', 'Bosnian'), 'bs'),
- (_('Brasilian Portuguese'), 'pb'),
- (D_('iso_639_3', 'Bulgarian'), 'bg'),
- (D_('iso_639_3', 'Catalan'), 'ca'),
- (D_('iso_639_3', 'Chinese'), 'zh'),
- (D_('iso_639_3', 'Croatian'), 'hr'),
- (D_('iso_639_3', 'Czech'), 'cs'),
- (D_('iso_639_3', 'Danish'), 'da'),
- (D_('iso_639_3', 'Dutch'), 'nl'),
- (D_('iso_639_3', 'English'), 'en'),
- (D_('iso_639_3', 'Esperanto'), 'eo'),
- (D_('iso_639_3', 'Estonian'), 'et'),
- (D_('iso_639_3', 'Finnish'), 'fi'),
- (D_('iso_639_3', 'French'), 'fr'),
- (D_('iso_639_3', 'Galician'), 'gl'),
- (D_('iso_639_3', 'Georgian'), 'ka'),
- (D_('iso_639_3', 'German'), 'de'),
- (D_('iso_639_3', 'Greek, Modern (1453-)'), 'el'),
- (D_('iso_639_3', 'Hebrew'), 'he'),
- (D_('iso_639_3', 'Hindi'), 'hi'),
- (D_('iso_639_3', 'Hungarian'), 'hu'),
- (D_('iso_639_3', 'Icelandic'), 'is'),
- (D_('iso_639_3', 'Indonesian'), 'id'),
- (D_('iso_639_3', 'Italian'), 'it'),
- (D_('iso_639_3', 'Japanese'), 'ja'),
- (D_('iso_639_3', 'Kazakh'), 'kk'),
- (D_('iso_639_3', 'Korean'), 'ko'),
- (D_('iso_639_3', 'Latvian'), 'lv'),
- (D_('iso_639_3', 'Lithuanian'), 'lt'),
- (D_('iso_639_3', 'Luxembourgish'), 'lb'),
- (D_('iso_639_3', 'Macedonian'), 'mk'),
- (D_('iso_639_3', 'Malay (macrolanguage)'), 'ms'),
- (D_('iso_639_3', 'Norwegian'), 'no'),
- (D_('iso_639_3', 'Occitan (post 1500)'), 'oc'),
- (D_('iso_639_3', 'Persian'), 'fa'),
- (D_('iso_639_3', 'Polish'), 'pl'),
- (D_('iso_639_3', 'Portuguese'), 'pt'),
- (D_('iso_639_3', 'Romanian'), 'ro'),
- (D_('iso_639_3', 'Russian'), 'ru'),
- (D_('iso_639_3', 'Serbian'), 'sr'),
- (D_('iso_639_3', 'Slovak'), 'sk'),
- (D_('iso_639_3', 'Slovenian'), 'sl'),
- (D_('iso_639_3', 'Spanish'), 'es'),
- (D_('iso_639_3', 'Swedish'), 'sv'),
- (D_('iso_639_3', 'Thai'), 'th'),
- (D_('iso_639_3', 'Turkish'), 'tr'),
- (D_('iso_639_3', 'Ukrainian'), 'uk'),
- (D_('iso_639_3', 'Vietnamese'), 'vi'),]
-
- # Map of ISO 639-1 language codes to the codes used by opensubtitles.org's API
- LANGUAGES = {'sq':'alb',
- 'ar':'ara',
- 'hy':'arm',
- 'ay':'ass',
- 'bs':'bos',
- 'pb':'pob',
- 'bg':'bul',
- 'ca':'cat',
- 'zh':'chi',
- 'hr':'hrv',
- 'cs':'cze',
- 'da':'dan',
- 'nl':'dut',
- 'en':'eng',
- 'eo':'epo',
- 'et':'est',
- 'fi':'fin',
- 'fr':'fre',
- 'gl':'glg',
- 'ka':'geo',
- 'de':'ger',
- 'el':'ell',
- 'he':'heb',
- 'hi':'hin',
- 'hu':'hun',
- 'is':'ice',
- 'id':'ind',
- 'it':'ita',
- 'ja':'jpn',
- 'kk':'kaz',
- 'ko':'kor',
- 'lv':'lav',
- 'lt':'lit',
- 'lb':'ltz',
- 'mk':'mac',
- 'ms':'may',
- 'no':'nor',
- 'oc':'oci',
- 'fa':'per',
- 'pl':'pol',
- 'pt':'por',
- 'ro':'rum',
- 'ru':'rus',
- 'sr':'scc',
- 'sk':'slo',
- 'sl':'slv',
- 'es':'spa',
- 'sv':'swe',
- 'th':'tha',
- 'tr':'tur',
- 'uk':'ukr',
- 'vi':'vie',}
-
- class SearchThread(threading.Thread):
- """
- This is the thread started when the dialog is searching for subtitles
- """
- def __init__(self, model):
- self.model = model
- self._done = False
- self._lock = threading.Lock()
- threading.Thread.__init__(self)
-
- def run(self):
- self.model.lock.acquire(True)
- self.model.results = self.model.os_search_subtitles()
- self.model.lock.release()
- self._done = True
-
- @property
- def done(self):
- """ Thread-safe property to know whether the query is done or not """
- self._lock.acquire(True)
- res = self._done
- self._lock.release()
- return res
-
- class DownloadThread(threading.Thread):
- """
- This is the thread started when the dialog is downloading the subtitles.
- """
- def __init__(self, model, subtitle_id):
- self.model = model
- self.subtitle_id = subtitle_id
- self._done = False
- self._lock = threading.Lock()
- threading.Thread.__init__(self)
-
- def run(self):
- self.model.lock.acquire(True)
- self.model.subtitles = self.model.os_download_subtitles(self.subtitle_id)
- self.model.lock.release()
- self._done = True
-
- @property
- def done(self):
- """ Thread-safe property to know whether the query is done or not """
- self._lock.acquire(True)
- res = self._done
- self._lock.release()
- return res
-
- # OpenSubtitles.org API abstraction
-
- class OpenSubtitlesModel(object):
- """
- This contains the logic of the opensubtitles service.
- """
- def __init__(self, server):
- self.server = server
- self.token = None
-
- try:
- import locale
- self.lang = LANGUAGES[locale.getlocale()[0].split('_')[0]]
- except:
- self.lang = 'eng'
- self.hash = None
- self.size = 0
-
- self.lock = threading.Lock()
- self.results = []
- self.subtitles = ''
-
- self.message = ''
-
- def os_login(self, username='', password=''):
- """
- Logs into the opensubtitles web service and gets a valid token for
- the comming comunications. If we are already logged it only checks
- the if the token is still valid.
-
- @rtype : bool
- """
- result = None
- self.message = ''
-
- if self.token:
- # We have already logged-in before, check the connection
- try:
- result = self.server.NoOperation(self.token)
- except:
- pass
- if result and result['status'] != OK200:
- return True
- try:
- result = self.server.LogIn(username, password, self.lang, USER_AGENT)
- except:
- pass
- if result and result.get('status') == OK200:
- self.token = result.get('token')
- if self.token:
- return True
-
- self.message = _('Could not contact the OpenSubtitles website')
-
- return False
-
- def os_search_subtitles(self):
- """
-
- """
- self.message = ''
- if self.os_login():
- searchdata = {'sublanguageid': self.lang,
- 'moviehash' : self.hash,
- 'moviebytesize': str(self.size)}
- try:
- result = self.server.SearchSubtitles(self.token, [searchdata])
- except xmlrpclib.ProtocolError:
- self.message = _('Could not contact the OpenSubtitles website')
-
- if result.get('data'):
- return result['data']
- else:
- self.message = _('No results found')
-
- return None
-
- def os_download_subtitles(self, subtitleId):
- """
- """
- self.message = ''
- if self.os_login():
- try:
- result = self.server.DownloadSubtitles(self.token, [subtitleId])
- except xmlrpclib.ProtocolError:
- self.message = _('Could not contact the OpenSubtitles website')
-
- if result and result.get('status') == OK200:
- try:
- subtitle64 = result['data'][0]['data']
- except:
- self.message = _('Could not contact the OpenSubtitles website')
- return None
-
- import StringIO, gzip, base64
- subtitleDecoded = base64.decodestring(subtitle64)
- subtitleGzipped = StringIO.StringIO(subtitleDecoded)
- subtitleGzippedFile = gzip.GzipFile(fileobj=subtitleGzipped)
-
- return subtitleGzippedFile.read()
-
- return None
-
-
- class OpenSubtitles(totem.Plugin):
- def __init__(self):
- totem.Plugin.__init__(self)
- self.dialog = None
- self.gconf_client = gconf.client_get_default()
- self.GCONF_BASE_DIR = "/apps/totem/plugins/opensubtitles/"
- self.GCONF_LANGUAGE = "language"
-
- # totem.Plugin methods
-
- def activate(self, totem_object):
- """
- Called when the plugin is activated.
- Here the sidebar page is initialized(set up the treeview, connect
- the callbacks, ...) and added to totem.
-
- @param totem_object:
- @type totem_object: {totem.TotemObject}
- """
- self.totem = totem_object
- self.filename = None
-
- self.manager = self.totem.get_ui_manager()
- self.os_append_menu()
-
- self.totem.connect('file-opened', self.on_totem__file_opened)
- self.totem.connect('file-closed', self.on_totem__file_closed)
-
- # Obtain the ServerProxy and init the model
- server = xmlrpclib.Server('http://www.opensubtitles.org/xml-rpc')
- self.model = OpenSubtitlesModel(server)
-
- def deactivate(self, totem):
- if self.dialog:
- self.dialog.destroy()
- self.dialog = None
-
- self.os_delete_menu()
-
- # UI related code
-
- def os_build_dialog(self, action, totem_object):
- builder = self.load_interface("opensubtitles.ui",
- True,
- self.totem.get_main_window(),
- self)
-
- # Obtain all the widgets we need to initialize
- combobox = builder.get_object('language_combobox')
- languages = builder.get_object('language_model')
- self.progress = builder.get_object('progress_bar')
- self.treeview = builder.get_object('subtitle_treeview')
- self.liststore = builder.get_object('subtitle_model')
- self.dialog = builder.get_object('subtitles_dialog')
- self.find_button = builder.get_object('find_button')
- self.apply_button = builder.get_object('apply_button')
- self.close_button = builder.get_object('close_button')
-
- # Set up and populate the languages combobox
- renderer = gtk.CellRendererText()
- sorted_languages = gtk.TreeModelSort(languages)
- sorted_languages.set_sort_column_id(0, gtk.SORT_ASCENDING)
- combobox.set_model(sorted_languages)
- combobox.pack_start(renderer, True)
- combobox.add_attribute(renderer, 'text', 0)
-
- self.model.lang = self.gconf_get_str (self.GCONF_BASE_DIR + self.GCONF_LANGUAGE, self.model.lang)
- for lang in LANGUAGES_STR:
- it = languages.append(lang)
- if LANGUAGES[lang[1]] == self.model.lang:
- parentit = sorted_languages.convert_child_iter_to_iter (None, it)
- combobox.set_active_iter(parentit)
-
- # Set up the results treeview
- renderer = gtk.CellRendererText()
- self.treeview.set_model(self.liststore)
- self.treeview.set_headers_visible(False)
- self.treeview.insert_column_with_attributes(0, _("Subtitles"), renderer, text=0)
- # translators comment:
- # This is the file-type of the subtitle file detected
- self.treeview.insert_column_with_attributes(1, _("Format"), renderer, text=1)
- # translators comment:
- # This is a rating of the quality of the subtitle
- self.treeview.insert_column_with_attributes(2, _("Rating"), renderer, text=2)
-
- self.apply_button.set_sensitive(False)
-
- self.apply_button.connect('clicked', self.on_apply_clicked)
- self.find_button.connect('clicked', self.on_find_clicked)
- self.close_button.connect('clicked', self.on_close_clicked)
-
- # Set up signals
-
- combobox_changed_id = combobox.connect('changed', self.on_combobox__changed)
- self.dialog.connect ('delete-event', self.dialog.hide_on_delete)
- self.dialog.set_transient_for (self.totem.get_main_window())
- self.dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
-
- # Connect the callback
- self.treeview.get_selection().connect('changed', self.on_treeview__row_change)
- self.treeview.connect('row-activated', self.on_treeview__row_activate)
-
- def os_show_dialog(self, action, totem_object):
- if not self.dialog:
- self.os_build_dialog(action, totem_object)
-
- filename = self.totem.get_current_mrl()
- if not self.model.results or filename != self.filename:
- self.filename = filename
-
- self.dialog.show_all()
-
- self.progress.set_fraction(0.0)
-
- def os_append_menu(self):
- """
- """
-
- self.os_action_group = gtk.ActionGroup('OpenSubtitles')
-
- self.action = gtk.Action('opensubtitles',
- _('_Download Movie Subtitles...'),
- _("Download movie subtitles from OpenSubtitles"),
- '')
-
- self.os_action_group.add_action(self.action)
-
- self.manager.insert_action_group(self.os_action_group, 0)
-
- self.menu_id = self.manager.new_merge_id()
- self.manager.add_ui(self.menu_id,
- '/tmw-menubar/view/subtitle-download-placeholder',
- 'opensubtitles',
- 'opensubtitles',
- gtk.UI_MANAGER_MENUITEM,
- False
- )
- self.action.set_visible(True)
-
- self.manager.ensure_update()
-
- self.action.connect('activate', self.os_show_dialog, self.totem)
-
- self.action.set_sensitive(self.totem.is_playing() and
- self.os_check_allowed_scheme() and
- not self.os_check_is_audio())
-
- def os_check_allowed_scheme(self):
- scheme = gio.File(self.totem.get_current_mrl()).get_uri_scheme()
- if scheme == 'dvd' or scheme == 'http' or scheme == 'dvb' or scheme == 'vcd':
- return False
- return True
-
- def os_check_is_audio(self):
- # FIXME need to use something else here
- # I think we must use video widget metadata but I don't found a way
- # to get this info from python
- filename = self.totem.get_current_mrl()
- if gio.content_type_guess(filename).split('/')[0] == 'audio':
- return True
- return False
-
- def os_delete_menu(self):
- self.manager.remove_action_group(self.os_action_group)
- self.manager.remove_ui(self.menu_id)
-
- def os_get_results(self):
- """
- """
- self.liststore.clear()
- self.treeview.set_headers_visible(False)
- self.model.results = []
- self.apply_button.set_sensitive(False)
- self.find_button.set_sensitive(False)
-
- self.dialog.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
-
- thread = SearchThread(self.model)
- thread.start()
- gobject.idle_add(self.os_populate_treeview)
-
- self.progress.set_text(_('Searching subtitles...'))
- gobject.timeout_add(350, self.os_progress_bar_increment, thread)
-
- def os_populate_treeview(self):
- """
- """
- if self.model.lock.acquire(False) == False:
- return True
-
- if self.model.results:
- self.apply_button.set_sensitive(True)
- for subData in self.model.results:
- if not SUBTITLES_EXT.count(subData['SubFormat']):
- continue
- self.liststore.append([subData['SubFileName'], subData['SubFormat'], subData['SubRating'], subData['IDSubtitleFile'],])
- self.treeview.set_headers_visible(True)
- else:
- self.apply_button.set_sensitive(False)
-
- self.model.lock.release()
-
- self.dialog.window.set_cursor(None)
-
- return False
-
- def os_save_selected_subtitle(self, filename=None):
- """
- """
- self.dialog.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
-
- model, rows = self.treeview.get_selection().get_selected_rows()
- if rows:
- iter = model.get_iter(rows[0])
- subtitle_id = model.get_value(iter, 3)
- subtitle_format = model.get_value(iter, 1)
-
- gfile = None
-
- if not filename:
- directory = gio.File(xdg.BaseDirectory.xdg_cache_home + sep + 'totem' + sep + 'subtitles' + sep)
- if not directory.query_exists():
- directory.make_directory()
-
- file = gio.File(self.filename)
- movie_name = file.get_basename().rpartition('.')[0]
- filename = directory.get_uri() + sep + movie_name + '.' + subtitle_format
-
- self.model.subtitles = ''
-
- thread = DownloadThread(self.model, subtitle_id)
- thread.start()
- gobject.idle_add(self.os_save_subtitles, filename)
-
- self.progress.set_text(_('Downloading the subtitles...'))
- gobject.timeout_add(350, self.os_progress_bar_increment, thread)
- else:
- #warn user!
- pass
-
- def os_save_subtitles(self, filename):
- if self.model.lock.acquire(False) == False:
- return True
-
- if self.model.subtitles:
- # Delete all previous cached subtitle for this file
- for ext in SUBTITLES_EXT:
- fp = gio.File(filename[:-3] + ext)
- if fp.query_exists():
- fp.delete()
-
- fp = gio.File(filename)
- suburi = fp.get_uri ()
-
- subFile = fp.replace('', False)
- subFile.write(self.model.subtitles)
- subFile.close()
-
- self.model.lock.release()
-
- self.dialog.window.set_cursor(None)
-
- if suburi:
- self.totem.set_current_subtitle(suburi)
-
- return False
-
- def os_progress_bar_increment(self, thread):
-
- if not thread.done:
- self.progress.pulse()
- return True
-
- if self.model.message:
- self.progress.set_text(self.model.message)
- else:
- self.progress.set_text('')
-
- self.progress.set_fraction(0.0)
- self.find_button.set_sensitive(True)
- self.apply_button.set_sensitive(False)
- self.treeview.set_sensitive(True)
- return False
-
- def os_download_and_apply(self):
- self.apply_button.set_sensitive(False)
- self.find_button.set_sensitive(False)
- self.action.set_sensitive(False)
- self.treeview.set_sensitive(False)
- self.os_save_selected_subtitle()
-
- # Callbacks
-
- def on_treeview__row_change(self, selection):
- if selection.count_selected_rows() > 0:
- self.apply_button.set_sensitive(True)
- else:
- self.apply_button.set_sensitive(False)
-
- def on_treeview__row_activate(self, path, column, data):
- self.os_download_and_apply()
-
- def on_totem__file_opened(self, totem, filename):
- """
- """
- # Check if allows subtitles
- if self.os_check_allowed_scheme() and not self.os_check_is_audio():
- self.action.set_sensitive(True)
- if self.dialog:
- self.find_button.set_sensitive(True)
- self.filename = self.totem.get_current_mrl()
- self.liststore.clear()
- self.treeview.set_headers_visible(False)
- self.apply_button.set_sensitive(False)
- self.results = []
- else:
- self.action.set_sensitive(False)
- if self.dialog and self.dialog.is_active():
- self.liststore.clear()
- self.treeview.set_headers_visible(False)
- self.apply_button.set_sensitive(False)
- self.find_button.set_sensitive(False)
-
- def on_totem__file_closed(self, totem):
- self.action.set_sensitive(False)
- if self.dialog:
- self.apply_button.set_sensitive(False)
- self.find_button.set_sensitive(False)
-
- def on_combobox__changed(self, combobox):
- iter = combobox.get_active_iter()
- self.model.lang = LANGUAGES[combobox.get_model().get_value(iter, 1)]
- self.gconf_set_str(self.GCONF_BASE_DIR + self.GCONF_LANGUAGE,
- self.model.lang)
-
- def on_close_clicked(self, data):
- self.dialog.destroy()
- self.dialog = None
-
- def on_apply_clicked(self, data):
- self.os_download_and_apply()
-
- def on_find_clicked(self, data):
- self.apply_button.set_sensitive(False)
- self.find_button.set_sensitive(False)
- self.filename = self.totem.get_current_mrl()
- self.model.hash , self.model.size = hashFile(self.filename)
-
- self.os_get_results()
-
- def gconf_get_str(self, key, default = ""):
- val = self.gconf_client.get(key)
-
- if val is not None and val.type == gconf.VALUE_STRING:
- return val.get_string()
- else:
- return default
-
- def gconf_set_str(self, key, val):
- self.gconf_client.set_string(key, val)
-
-